Skip to content

feat(miniapp): 实现小程序加密网络通道服务端支持,修复 HMAC 签名与错误处理 Bug#3969

Open
Copilot wants to merge 3 commits intodevelopfrom
copilot/add-encrypted-network-channel
Open

feat(miniapp): 实现小程序加密网络通道服务端支持,修复 HMAC 签名与错误处理 Bug#3969
Copilot wants to merge 3 commits intodevelopfrom
copilot/add-encrypted-network-channel

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 22, 2026

微信小程序加密网络通道(getUserEncryptKey)API 已有基础实现,但存在签名计算错误、错误码判断缺陷,且缺少配套的 AES 加解密工具方法。

Bug 修复

WxMaInternetServiceImpl

  • HMAC-SHA256 key 格式错误sha256() 原先用 sessionKey.getBytes(UTF_8)(base64 字符串的 UTF-8 字节)作为 HMAC key,应改为 Base64.decodeBase64(sessionKey)(原始密钥字节),与微信其他加密 API 惯例一致
  • 错误码判断不完整errcode == -1 仅捕获单一错误码且有 NPE 风险,改为 errcode != null && errcode != 0

新增功能

WxMaCryptUtils 新增针对加密网络通道的 AES-128-CBC 工具方法,支持 getUserEncryptKey 返回的 Base64 编码 key 和 Hex 编码 iv:

// 服务端解密小程序加密请求体
String encryptKey = keyInfo.getEncryptKey();  // Base64,来自 getUserEncryptKey
String hexIv      = keyInfo.getIv();          // Hex
String plainText  = WxMaCryptUtils.decryptWithEncryptKey(encryptKey, hexIv, encryptedBody);

// 服务端加密响应体
String encrypted = WxMaCryptUtils.encryptWithEncryptKey(encryptKey, hexIv, responseJson);

hexToBytes() 内部辅助方法含输入校验(奇数长度、非法字符),非法输入抛出 IllegalArgumentException

Copilot AI linked an issue Apr 22, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Add encrypted network channel for mini program feat(miniapp): 实现小程序加密网络通道服务端支持,修复 HMAC 签名与错误处理 Bug Apr 22, 2026
Copilot AI requested a review from binarywang April 22, 2026 13:31
@binarywang binarywang marked this pull request as ready for review April 23, 2026 07:04
Copilot AI review requested due to automatic review settings April 23, 2026 07:04
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 23, 2026

🤖 Augment PR Summary

Summary: This PR improves MiniApp “加密网络通道/getUserEncryptKey” server-side support by fixing signature calculation and strengthening error handling.

Changes:

  • Fixes HMAC-SHA256 signing to use the decoded session_key bytes as the HMAC key (instead of UTF-8 bytes of the Base64 string).
  • Updates error-code handling to treat any non-zero errcode as an error while avoiding NPE on null errcode.
  • Adds AES-128-CBC encrypt/decrypt helpers for the channel’s encrypt_key (Base64) + iv (Hex) values.
  • Adds Hex parsing validation helper and unit tests covering encrypt/decrypt round-trip.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

new SecretKeySpec(keyBytes, "AES"),
new IvParameterSpec(ivBytes));
return new String(cipher.doFinal(dataBytes), UTF_8);
} catch (Exception e) {
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decryptWithEncryptKey catches all Exception and wraps it into WxRuntimeException, which means invalid hexIv (from hexToBytes’s IllegalArgumentException) won’t be observable as IllegalArgumentException by callers and the specific validation message is lost; is that intended given the Javadoc contract on hexToBytes? Other locations where this applies: weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java:141.

Severity: medium

Other Locations
  • weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java:141

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

public void testEncryptAndDecryptWithEncryptKey() {
String plainText = "{\"userId\":\"12345\",\"amount\":100}";

String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText);
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests only assert encrypt/decrypt round-trip symmetry, so they can still pass even if the algorithm/encoding doesn’t match WeChat’s expected wire format (both sides could be consistently wrong). Consider adding at least one fixed test vector (known plaintext → expected ciphertext, or vice versa) from the official doc/examples to validate interoperability.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee161b56f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

public static String decryptWithEncryptKey(String encryptKey, String hexIv, String encryptedData) {
try {
byte[] keyBytes = Base64.decodeBase64(encryptKey);
byte[] ivBytes = hexToBytes(hexIv);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle getUserEncryptKey iv as 16-byte IV

decryptWithEncryptKey unconditionally hex-decodes iv (hexToBytes), but the project’s own getUserEncryptKey response examples use 16-character values like 6003f73ec441c386 (WxMaInternetResponse/WxMaInternetUserKeyInfo), which become only 8 bytes after hex decoding; AES-CBC requires a 16-byte IV, so this path will throw at runtime for documented API-shaped inputs and make the new encrypt/decrypt helpers unusable in that scenario.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 面向 weixin-java-miniapp 模块补齐/修复小程序加密网络通道(getUserEncryptKey)服务端支持:修正 HMAC 签名 key 的取值方式、完善错误码判断,并新增基于 encrypt_key + iv 的 AES-CBC 加解密工具,配套新增单测验证基础对称性。

Changes:

  • 修复 WxMaInternetServiceImpl 的 HMAC-SHA256 key 取值(Base64 解码为原始密钥字节)与错误码判断(非 0 且非 null 才视为错误)。
  • WxMaCryptUtils 新增加密网络通道专用的 AES-128-CBC 加/解密方法与 Hex iv 转换辅助方法。
  • 为新增加/解密方法补充对称性单测。

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java 修正签名计算的 HMAC key 字节来源,并加强 errcode 判错逻辑
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java 新增加密网络通道 AES-CBC 加/解密能力与 Hex iv 转字节工具
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java 增加加密网络通道加/解密对称性测试

Comment on lines +101 to +114
public static String decryptWithEncryptKey(String encryptKey, String hexIv, String encryptedData) {
try {
byte[] keyBytes = Base64.decodeBase64(encryptKey);
byte[] ivBytes = hexToBytes(hexIv);
byte[] dataBytes = Base64.decodeBase64(encryptedData);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE,
new SecretKeySpec(keyBytes, "AES"),
new IvParameterSpec(ivBytes));
return new String(cipher.doFinal(dataBytes), UTF_8);
} catch (Exception e) {
throw new WxRuntimeException("AES解密失败!", e);
}
Comment on lines +131 to +143
public static String encryptWithEncryptKey(String encryptKey, String hexIv, String data) {
try {
byte[] keyBytes = Base64.decodeBase64(encryptKey);
byte[] ivBytes = hexToBytes(hexIv);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(keyBytes, "AES"),
new IvParameterSpec(ivBytes));
return Base64.encodeBase64String(cipher.doFinal(data.getBytes(UTF_8)));
} catch (Exception e) {
throw new WxRuntimeException("AES加密失败!", e);
}
Comment on lines +17 to +20
// 模拟来自 getUserEncryptKey 接口返回的 encrypt_key(Base64)和 iv(Hex,32位即16字节)
private static final String ENCRYPT_KEY = "VI6BpyrK9XH4i4AIGe86tg==";
private static final String HEX_IV = "6003f73ec441c3866003f73ec441c386";

Comment on lines +44 to +65
@Test
public void testEncryptAndDecryptWithEncryptKey() {
String plainText = "{\"userId\":\"12345\",\"amount\":100}";

String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText);
assertThat(encrypted).isNotNull().isNotEmpty();

String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted);
assertThat(decrypted).isEqualTo(plainText);
}

/**
* 测试加密网络通道的加解密对称性(不同明文).
*/
@Test
public void testEncryptDecryptSymmetryWithEncryptKey() {
String plainText = "hello miniprogram";

String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText);
String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted);
assertThat(decrypted).isEqualTo(plainText);
}
Comment on lines +153 to +166
private static byte[] hexToBytes(String hex) {
if (hex == null || hex.length() % 2 != 0) {
throw new IllegalArgumentException("无效的十六进制字符串格式:长度必须为偶数");
}
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int high = Character.digit(hex.charAt(i), 16);
int low = Character.digit(hex.charAt(i + 1), 16);
if (high == -1 || low == -1) {
throw new IllegalArgumentException("无效的十六进制字符串格式:包含非法字符 '" + hex.charAt(high == -1 ? i : i + 1) + "'");
}
data[i / 2] = (byte) ((high << 4) + low);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

小程序加密网络通道

3 participants